﻿using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Threading;
using Contracts;
using Microsoft.WindowsAPICodePack.Sensors;

namespace MefSeismoAddin
{
	[WpfServiceMetadata("SeismoAddin")]
	public class SeismoAddinImpl : IWpfService, INotifyPropertyChanged
	{
		public event EventHandler StatusUpdated;
		private string _status;
		
		private const string PerfCategoryName = "Sensors";
		private const string PerfNetForceName = "Net force";
		private PerformanceCounterCategory _category = null;
		private PerformanceCounter _perfCounter = null;

		private Thread t;

		public ObservableCollection<SensorReading> ReadingsX { get; set; }
		public ObservableCollection<SensorReading> ReadingsY { get; set; }
		public ObservableCollection<SensorReading> ReadingsZ { get; set; }
		private Accelerometer3D _accelSensor = null;
		
		private readonly SeismoUserControl _optionsControl = new SeismoUserControl();

		public SeismoAddinImpl()
		{
			Console.WriteLine("Seismometer tracker starting up!");
			ReadingsX = new ObservableCollection<SensorReading>();
			ReadingsY = new ObservableCollection<SensorReading>();
			ReadingsZ = new ObservableCollection<SensorReading>();
		}

		private void CreatePerfCounter()
		{
			if (PerformanceCounterCategory.Exists(PerfCategoryName))
			{
				PerformanceCounterCategory.Delete(PerfCategoryName);
			}

			// Create a collection of type CounterCreationDataCollection.
			var counterCreationData = new CounterCreationDataCollection();

			// Create the counter, set properties, and add to collection
			counterCreationData.Add(new CounterCreationData
			{
				CounterName = PerfNetForceName,
				CounterHelp = "The net acceleration value (in G's)",
				CounterType = PerformanceCounterType.NumberOfItems64
			});

			// Create the category and pass the collection to it.
			_category =
				PerformanceCounterCategory.Create(
				PerfCategoryName,
				"The Sensors performance object tracks the value of various sensors over time",
				PerformanceCounterCategoryType.SingleInstance,
				counterCreationData);

			_perfCounter = new PerformanceCounter(PerfCategoryName, PerfNetForceName, false);
		}

		#region IWpfService Impl
		public System.Windows.Controls.UserControl OptionsUserControl
		{
			get { return _optionsControl; }
		}

		public string Name
		{
			get { return "Sensor Seismometer"; }
		}

		public Uri AddinIconUri
		{
			get {
				string u = CreateResourceUri("chart.png");

				return new Uri(u);
			}
		}

		private static string CreateResourceUri(string name)
		{
			Assembly a = Assembly.GetExecutingAssembly();

			return string.Format("pack://application:,,,/{0};component/Resources/{1}", a.GetName().Name, name);
		}

		public string Description
		{
			get { return "Writes sensor values to a performance counter and can sound an alarm"; }
		}

		public string Author
		{
			get { return "Arian T. Kulp"; }
		}

		public Version Version
		{
			get { return System.Reflection.Assembly.GetExecutingAssembly().GetName().Version; }
		}

		public Uri AuthorUri
		{
			get { return new Uri("http://www.ariankulp.com/"); }
		}

		public Uri UpdateUri
		{
			get { return new Uri("http://www.ariankulp.com/downloads/"); }
		}

		public string Status
		{
			get { return _status; }
			set
			{
				if (value != _status)
				{
					_status = value;

					if (StatusUpdated != null)
						StatusUpdated(this, null);
				}
			}
		}
		#endregion

		public void Initialize()
		{
			_optionsControl.DataContext = this;
		}


		public void Start()
		{

			if (!EnvironmentEx.MeetsOrExceeds(PlatformVersion.WINDOWS_7))
			{
				// Fatal error
				throw new UtilityRuntimeException("The seismometer addin requires Windows 7 or higher to operate");
			}
			else
			{
				try
				{
					CreatePerfCounter();
					LocateSensors();
					this.Sensitivity = 1.5;

					Status = "Seismometer Sensor Started";
				}
				catch (Exception x)
				{
					Status = "Error creating performance counter: " + x.Message;
				}
			}
		}

		private void LocateSensors()
		{
			try
			{
				var sensors = SensorManager.GetSensorsByTypeId<Accelerometer3D>();
				if (sensors == null) Status = "No sensors found";
				else
				{
					Status = "Found " + sensors.Count + " sensors";

					if (sensors.Count > 0)
					{
						var s2 = new SensorList<Sensor> {sensors[0]};
						SensorManager.RequestPermission(IntPtr.Zero, false, s2);
						PrepareSensor(sensors[0]);
					}

				}
			}
			catch (SensorPlatformException x)
			{
				Status = x.Message;
			}

			SensorManager.SensorsChanged += new SensorsChangedEventHandler(SensorManagerSensorsChanged);
		}

		void PrepareSensor(Accelerometer3D sensor)
		{
			_accelSensor = sensor;

			if (sensor.State == SensorState.Ready)
			{
				sensor.AutoUpdateDataReport = false;
				this.SensorName = sensor.Manufacturer + " " + sensor.Model + " (" + sensor.FriendlyName + ")";
	
				t = new Thread(new ThreadStart(() =>
				{
					while (_accelSensor != null)
					{
						UpdateData();
						Thread.Sleep(250);
					}
				})) { IsBackground = true };
				t.Start();
			}
			else
				sensor.StateChanged += new StateChangedEventHandler(SensorStateChanged);
		}

		#region Public properties
		#region Magnitude Property
		private double _magnitude;
		public double Magnitude
		{
			get { return _magnitude; }
			set
			{
				_magnitude = value;
				OnPropertyChanged("Magnitude");
			}
		}
		#endregion

		#region LastDataReportX Property
		private float _lastDataReportX;
		public float LastDataReportX
		{
			get { return _lastDataReportX; }
			set
			{
				_lastDataReportX = value;
				OnPropertyChanged("LastDataReportX");
			}
		}
		#endregion

		#region LastDataReportY Property
		private float _lastDataReportY;
		public float LastDataReportY
		{
			get { return _lastDataReportY; }
			set
			{
				_lastDataReportY = value;
				OnPropertyChanged("LastDataReportY");
			}
		}
		#endregion

		#region LastDataReportZ Property
		private float _lastDataReportZ;
		public float LastDataReportZ
		{
			get { return _lastDataReportZ; }
			set
			{
				_lastDataReportZ = value;
				OnPropertyChanged("LastDataReportZ");
			}
		}
		#endregion

		#region Sensitivity Property
		private double _sensitivity;
		public double Sensitivity
		{
			get { return _sensitivity; }
			set
			{
				_sensitivity = value;
				OnPropertyChanged("Sensitivity");
			}
		}
		#endregion
	
		#region SensorName Property
		private string _sensorName;
		public string SensorName
		{
			get { return _sensorName; }
			set
			{
				_sensorName = value;
				OnPropertyChanged("SensorName");
			}
		}
		#endregion
		#endregion

		void UpdateData()
		{
			_optionsControl.Dispatcher.BeginInvoke(new Action(
				delegate()
			    {
					if ((_accelSensor == null) || (_accelSensor.TryUpdateData() == false)) return;
					
					var currentAccel = _accelSensor.CurrentAcceleration;
					DateTime sampleTime = DateTime.Now;

					this.LastDataReportX = currentAccel[AccelerationAxis.X];
					this.LastDataReportY = currentAccel[AccelerationAxis.Y];
					this.LastDataReportZ = currentAccel[AccelerationAxis.Z];

					this.Magnitude =
						Math.Sqrt(Math.Pow(this.LastDataReportX, 2) + Math.Pow(this.LastDataReportY, 2) +
							        Math.Pow(this.LastDataReportZ, 2));

					if (_perfCounter != null) _perfCounter.RawValue = Convert.ToInt64(this.Magnitude * 10);

					ReadingsX.Add(new SensorReading { Reading = this.LastDataReportX, Timestamp = sampleTime });
					ReadingsY.Add(new SensorReading { Reading = this.LastDataReportY, Timestamp = sampleTime });
					ReadingsZ.Add(new SensorReading { Reading = this.LastDataReportZ, Timestamp = sampleTime });

					if (ReadingsX.Count > 20) ReadingsX.RemoveAt(0);
					if (ReadingsY.Count > 20) ReadingsY.RemoveAt(0);
					if (ReadingsZ.Count > 20) ReadingsZ.RemoveAt(0);

					if (this.Magnitude > this.Sensitivity)
					{ 
						Status = string.Format("Big movement! ({0} G's)", Magnitude);
						System.Media.SystemSounds.Exclamation.Play();
					}
			}));
		}

		void SensorStateChanged(Sensor sender, EventArgs e)
		{
			PrepareSensor(sender as Accelerometer3D);
		}

		void SensorManagerSensorsChanged(SensorsChangedEventArgs e)
		{
			if (e.Change == SensorAvailabilityChange.Removal)
			{
				_accelSensor = null;
				LastDataReportX = 0;
				LastDataReportY = 0;
				LastDataReportZ = 0;
				Magnitude = 0;
				Status = "Sensor disconnected";
				ReadingsX.Clear();
				ReadingsY.Clear();
				ReadingsZ.Clear();
			}
			else if (e.Change == SensorAvailabilityChange.Addition)
			{
				// s will be null if new sensor is not the right type
				var s = SensorManager.GetSensorBySensorId<Accelerometer3D>(e.SensorId);
				if (s != null) PrepareSensor(s);
				Status = "Sensor added";
			}
		}

		public void Stop()
		{
			_accelSensor = null;
			Status = "Seismometer Stopped";
		}

		#region IWpfService Members


		public bool HideOnClose
		{
			get
			{
				return true;
			}
		}

		#endregion

		#region INotifyPropertyChanged Members

		public event PropertyChangedEventHandler PropertyChanged;

		private void OnPropertyChanged(string prop)
		{
			if (PropertyChanged != null)
				PropertyChanged(this, new PropertyChangedEventArgs(prop));
		}

		#endregion
	}
}
